package at.fhooe.mc;

import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.powermock.api.easymock.PowerMock.mockStatic;
import java.lang.reflect.Field;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import at.fhooe.mc.control.ElevatorControlCenter;
import at.fhooe.mc.control.IAdapter;
import at.fhooe.mc.control.automation.IAutoAlgorithm;
import at.fhooe.mc.control.exception.SameClockTickException;
import at.fhooe.mc.data.ControlMode;
import at.fhooe.mc.data.DirectionType;
import at.fhooe.mc.data.DoorState;
import at.fhooe.mc.data.ElevatorData;
import at.fhooe.mc.data.FloorData;
import at.fhooe.mc.data.SystemData;
import at.fhooe.mc.data.control.ElevatorControlData;
import at.fhooe.mc.model.ElevatorControlCenterObserver;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ ElevatorControlCenter.class, JOptionPane.class, System.class})
public class PowerMockElevatorControlTest
{

	private ElevatorControlCenter ecc;

	private IAdapter mock;
	private IMocksControl ctrl;

	private ElevatorControlCenterObserver mockObserver;
	private IAutoAlgorithm mockAutoAlgorithm;

	private SystemData curSystemData;
	private List<ElevatorControlData> curControlData;

	private List<ElevatorControlData> getDefaultControlData()
	{
		List<ElevatorControlData> controlData = new ArrayList<ElevatorControlData>();

		ElevatorControlData elevControl = new ElevatorControlData();
		elevControl.setMode(ControlMode.AUTOMATIC);
		elevControl.setNewTarget(2);
		controlData.add(elevControl);

		elevControl = new ElevatorControlData();
		elevControl.setMode(ControlMode.MANUAL);
		elevControl.setNewTarget(3);
		controlData.add(elevControl);

		return controlData;
	}

	private SystemData getDefaultSystemData()
	{
		SystemData defaultData = new SystemData();
		defaultData.setClockTick(1000L);
		defaultData.setFloorHeight(20);

		FloorData f = new FloorData();
		f.setFloorIndex(0);
		f.setFloorButtonDown(false);
		f.setFloorButtonUp(true);
		defaultData.getFloors().add(f);

		f = new FloorData();
		f.setFloorIndex(1);
		f.setFloorButtonDown(true);
		f.setFloorButtonUp(false);
		defaultData.getFloors().add(f);

		f = new FloorData();
		f.setFloorIndex(2);
		f.setFloorButtonDown(true);
		f.setFloorButtonUp(true);
		defaultData.getFloors().add(f);

		f = new FloorData();
		f.setFloorIndex(3);
		f.setFloorButtonDown(false);
		f.setFloorButtonUp(false);
		defaultData.getFloors().add(f);

		ElevatorData defaultElevator = new ElevatorData(4,
			ControlMode.AUTOMATIC);
		defaultElevator.setElevatorIndex(0);
		defaultElevator.setElevatorFloor(2);
		defaultElevator.setElevatorPosition(10);
		defaultElevator.setCurrentTarget(3);
		defaultElevator.setCommittedDirection(DirectionType.UP);
		defaultElevator.setElevatorDoorStatus(DoorState.OPEN);
		defaultElevator.setElevatorSpeed(23);
		defaultElevator.setElevatorAccel(12);
		defaultElevator.setElevatorCapacity(200);
		defaultElevator.setElevatorWeight(100);

		defaultElevator.getElevatorButton()[0] = true;
		defaultElevator.getElevatorButton()[1] = false;
		defaultElevator.getElevatorButton()[2] = true;
		defaultElevator.getElevatorButton()[3] = false;

		defaultElevator.getServicesFloors()[0] = false;
		defaultElevator.getServicesFloors()[1] = false;
		defaultElevator.getServicesFloors()[2] = true;
		defaultElevator.getServicesFloors()[3] = true;

		defaultData.getElevators().add(defaultElevator);

		ElevatorData e = new ElevatorData(4, ControlMode.MANUAL);
		e.setElevatorIndex(1);
		e.setElevatorFloor(1);
		e.setElevatorPosition(2);
		e.setCurrentTarget(0);
		e.setCommittedDirection(DirectionType.DOWN);
		e.setElevatorDoorStatus(DoorState.OPENING);
		e.setElevatorSpeed(0);
		e.setElevatorAccel(-1);
		e.setElevatorCapacity(220);
		e.setElevatorWeight(90);

		e.getElevatorButton()[0] = false;
		e.getElevatorButton()[1] = true;
		e.getElevatorButton()[2] = false;
		e.getElevatorButton()[3] = true;

		e.getServicesFloors()[0] = true;
		e.getServicesFloors()[1] = true;
		e.getServicesFloors()[2] = false;
		e.getServicesFloors()[3] = false;

		defaultData.getElevators().add(e);

		return defaultData;
	}

	@Before
	public void setup() throws SecurityException, NoSuchFieldException,
		IllegalArgumentException, IllegalAccessException, NoSuchMethodException
	{
		ecc = new ElevatorControlCenter();

		curSystemData = getDefaultSystemData();

		Field fieldControlData = ElevatorControlCenter.class.getDeclaredField("controlElevatorData");
		fieldControlData.setAccessible(true);
		curControlData = getDefaultControlData();
		fieldControlData.set(ecc, curControlData);

		ctrl = EasyMock.createStrictControl();
		mock = ctrl.createMock(IAdapter.class);

		mockObserver = ctrl.createMock(ElevatorControlCenterObserver.class);

		mockAutoAlgorithm = ctrl.createMock(IAutoAlgorithm.class);

		ecc.setAdapter(mock);
		ecc.setAutoAlgorithm(mockAutoAlgorithm);
	}

	@Test
	public void testStart() throws Exception
	{
		mockStatic(JOptionPane.class);

		expect(mock.init()).andReturn(true);

		expect(mock.parseData(null, null)).andReturn(curSystemData);

		mockAutoAlgorithm.init(curSystemData);

		mockObserver.updateNewValues(curSystemData);

		mockAutoAlgorithm.newValuesAvailable(curSystemData, ecc);

		expect(mock.updateNewTargets(curSystemData, curControlData)).andThrow(
			new RemoteException());

		JOptionPane.showMessageDialog(null,
			"Connection Problem with the Remote Elevator Device", "Error",
			JOptionPane.ERROR_MESSAGE);

		ctrl.replay();
		PowerMock.replay(JOptionPane.class);

		ecc.registerObserver(mockObserver);

		curControlData.clear();

		assertTrue(ecc.start());

		Thread.yield();

		// give the other thread some time to complete the task
		Thread.sleep(300);

		ctrl.verify();
		PowerMock.verify(JOptionPane.class);
	}

	@Test
	public void testRun() throws Exception
	{
		mockStatic(JOptionPane.class);

		expect(mock.parseData(null, null)).andReturn(curSystemData);

		mockAutoAlgorithm.init(curSystemData);

		mockObserver.updateNewValues(curSystemData);

		mockAutoAlgorithm.newValuesAvailable(curSystemData, ecc);

		expect(mock.updateNewTargets(curSystemData, curControlData)).andThrow(
			new RemoteException());

		JOptionPane.showMessageDialog(null,
			"Connection Problem with the Remote Elevator Device", "Error",
			JOptionPane.ERROR_MESSAGE);

		ctrl.replay();
		PowerMock.replay(JOptionPane.class);

		ecc.registerObserver(mockObserver);

		curControlData.clear();

		ecc.run();

		Thread.yield();
		ctrl.verify();

		PowerMock.verify(JOptionPane.class);

	}

	@Test
	public void testRunMultipleLoops() throws Exception
	{
		mockStatic(JOptionPane.class);

		expect(mock.parseData(null, null)).andReturn(curSystemData);

		mockAutoAlgorithm.init(curSystemData);

		mockObserver.updateNewValues(curSystemData);

		mockAutoAlgorithm.newValuesAvailable(curSystemData, ecc);

		// first loop
		expect(mock.updateNewTargets(curSystemData, curControlData)).andReturn(
			true);

		SystemData newData = getDefaultSystemData();
		newData.setClockTick(200);
		newData.setFloorHeight(1);
		expect(mock.parseData(curSystemData, curControlData)).andReturn(newData);

		mockObserver.updateNewValues(newData);

		mockAutoAlgorithm.newValuesAvailable(newData, ecc);

		// second loop
		expect(mock.updateNewTargets(newData, curControlData)).andThrow(
			new RemoteException());
		JOptionPane.showMessageDialog(null,
			"Connection Problem with the Remote Elevator Device", "Error",
			JOptionPane.ERROR_MESSAGE);

		ctrl.replay();
		PowerMock.replay(JOptionPane.class);

		ecc.registerObserver(mockObserver);

		curControlData.clear();

		ecc.run();

		ctrl.verify();

		PowerMock.verify(JOptionPane.class);

		assertEquals(-1, curControlData.get(0).getNewTarget());
		assertEquals(-1, curControlData.get(0).getNewTarget());

	}

	@Test
	public void testRunInterruptException() throws Exception
	{
		mockStatic(JOptionPane.class);

		expect(mock.parseData(null, null)).andReturn(curSystemData);

		mockAutoAlgorithm.init(curSystemData);

		mockObserver.updateNewValues(curSystemData);

		mockAutoAlgorithm.newValuesAvailable(curSystemData, ecc);

		expect(mock.updateNewTargets(curSystemData, curControlData)).andThrow(
			new InterruptedException());

		JOptionPane.showMessageDialog(
			null,
			"An fatal error occured. Check the settings of the application and the remote device",
			"Error", JOptionPane.ERROR_MESSAGE);

		ctrl.replay();
		PowerMock.replay(JOptionPane.class);

		ecc.registerObserver(mockObserver);

		curControlData.clear();

		ecc.run();

		ctrl.verify();

		PowerMock.verify(JOptionPane.class);

	}

	@Test
	public void testRunInitException() throws Exception
	{
		mockStatic(JOptionPane.class);

		expect(mock.parseData(null, null)).andThrow(new InterruptedException());

		JOptionPane.showMessageDialog(null,
			"Unable to initialize the application", "Error",
			JOptionPane.ERROR_MESSAGE);

		ctrl.replay();
		PowerMock.replay(JOptionPane.class);

		ecc.run();

		ctrl.verify();

		PowerMock.verify(JOptionPane.class);

	}

	@Test
	public void testRunSameClockException() throws Exception
	{
		mockStatic(JOptionPane.class);

		expect(mock.parseData(null, null)).andReturn(curSystemData);

		mockAutoAlgorithm.init(curSystemData);

		mockObserver.updateNewValues(curSystemData);

		mockAutoAlgorithm.newValuesAvailable(curSystemData, ecc);

		// first loop
		expect(mock.updateNewTargets(curSystemData, curControlData)).andReturn(
			true);

		expect(mock.parseData(curSystemData, curControlData)).andThrow(
			new SameClockTickException());

		JOptionPane.showMessageDialog(
			null,
			"Remote Divice is not working correctly. No valid Clock Tick Update received.",
			"Error", JOptionPane.ERROR_MESSAGE);

		// second loop
		expect(mock.updateNewTargets(curSystemData, curControlData)).andThrow(
			new RemoteException());
		JOptionPane.showMessageDialog(null,
			"Connection Problem with the Remote Elevator Device", "Error",
			JOptionPane.ERROR_MESSAGE);

		ctrl.replay();
		PowerMock.replay(JOptionPane.class);

		ecc.registerObserver(mockObserver);

		curControlData.clear();

		ecc.run();

		ctrl.verify();

		PowerMock.verify(JOptionPane.class);

		assertEquals(-1, curControlData.get(0).getNewTarget());
		assertEquals(-1, curControlData.get(0).getNewTarget());

	}

	@Test
	public void testRunSameClockExceptionDeltaTimeTooSHort() throws Exception
	{
		mockStatic(JOptionPane.class);
		mockStatic(System.class);

		expect(mock.parseData(null, null)).andReturn(curSystemData);

		mockAutoAlgorithm.init(curSystemData);

		mockObserver.updateNewValues(curSystemData);

		mockAutoAlgorithm.newValuesAvailable(curSystemData, ecc);

		// first loop
		expect(mock.updateNewTargets(curSystemData, curControlData)).andReturn(
			true);

		expect(mock.parseData(curSystemData, curControlData)).andThrow(
			new SameClockTickException());

		expect(System.currentTimeMillis()).andReturn((long) (1000));

		// second loop
		expect(mock.updateNewTargets(curSystemData, curControlData)).andThrow(
			new RemoteException());
		JOptionPane.showMessageDialog(null,
			"Connection Problem with the Remote Elevator Device", "Error",
			JOptionPane.ERROR_MESSAGE);

		ctrl.replay();
		PowerMock.replay(JOptionPane.class, System.class);

		ecc.registerObserver(mockObserver);

		curControlData.clear();

		ecc.run();

		ctrl.verify();

		PowerMock.verifyAll();

		assertEquals(-1, curControlData.get(0).getNewTarget());
		assertEquals(-1, curControlData.get(0).getNewTarget());

	}
}
